﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SimplyConfigured.Sources;

namespace SimplyConfigured.Tests
{
    /// <summary>
    /// Summary description for UnitTest1
    /// </summary>
    [TestClass]
    public class ConfigurationServiceTest
    {
        public ConfigurationServiceTest()
        {
            //
            // TODO: Add constructor logic here
            //
        }
        
        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        [TestMethod]
        public void Simple_usage_test()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            var val = config["testKey"];
            Assert.AreEqual("testValue", val);
        }

        [TestMethod]
        public void Configuration_change_event_will_not_fire_for_existing_key_without_value_change()
        {
            var actual = string.Empty;
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            config["existsnow"] = "test";
            config.ConfigurationDataChanged += delegate(object sender, ConfigurationDataChangedEventArgs e)
                                                {
                                                    actual = e.Data;
                                                };

            config["existsnow"] = "test";
            Assert.AreEqual(string.Empty, actual);
        }

        [TestMethod]
        public void Configuration_change_event_fires_correctly_for_existing_key_and_supplies_correct_data()
        {
            var actual = string.Empty;
            var testVal = "some new value";
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            config["existsnow"] = "test";
            config.ConfigurationDataChanged += delegate(object sender, ConfigurationDataChangedEventArgs e)
                                                {
                                                    actual = e.Data;
                                                };

            config["existsnow"] = testVal;
            Assert.AreEqual(testVal, actual);
        }

        [TestMethod]
        public void Configuration_change_event_fires_correctly_for_new_key_and_supplies_correct_data()
        {
            var actual = "";
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            config.ConfigurationDataChanged += delegate(object sender, ConfigurationDataChangedEventArgs e)
                                                {
                                                    actual = e.Data;
                                                };

            config["doesntexist"] = "test";
            Assert.AreEqual("test", actual);
        }

        [TestMethod]
        public void Configuration_change_event_fires_only_once_per_data_change()
        {
            var actual = new List<string>();
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            config.ConfigurationDataChanged += delegate(object sender, ConfigurationDataChangedEventArgs e)
                                                {
                                                    actual.Add(e.Data);
                                                };

            config["doesntexist"] = "test";
            // values was assigned correctly:
            Assert.AreEqual(config["doesntexist"], "test");
            Assert.AreEqual(1, actual.Count);

            config["doesntexisteither"] = "test";
            // values is still assigned correctly:
            Assert.AreEqual(config["doesntexist"], "test");
            // values was assigned correctly:
            Assert.AreEqual(config["doesntexisteither"], "test");
            Assert.AreEqual(2, actual.Count);
        }

        //[TestMethod]
        //public void ConfigurationSource_change_event_fires_after_file_change()
        //{
        //    var counter = 0;
        //    var cls = new IniConfigurationSource();

        //    cls.ConfigurationSourceChanged += cls_ConfigurationSourceChanged;                   // (sender, e) => counter++;
        //    string ini = writeToIni(new Uri(Path.GetDirectoryName(Assembly.GetAssembly(typeof(IniConfigurationSource)).CodeBase) + "\\App.ini").LocalPath);
        //    //Assert.IsTrue(counter > 0);
        //}

        //void cls_ConfigurationSourceChanged(object sender, ConfigurationSourceChangedEventArgs e)
        //{
        //    var sndr = sender.GetType();    
        //}

        //[TestMethod]
        //public void Configuration_Factory_change_event_fires_after_file_change()
        //{
            
        //}

        private string writeToIni(string fileName)
        {
            var autoKey = "AutoKey" + DateTime.Now.Ticks.ToString();
            using (var writer = new StreamWriter(fileName, true))
            {
                writer.WriteLine("");
                writer.Write("{0}={1}", autoKey, "blah");
            }
            return autoKey;

        }

        [TestMethod]
        public void GetList_returns_correctly_split_values()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            var list = config.GetCollection("testList");

            Assert.IsTrue(list.Count == 4);
        }

        [TestMethod]
        public void GetList_trims_spaces_around_values()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            var list = config.GetCollection("Trim Whitespaces");

            foreach (var item in list)
            {
                Assert.IsFalse(HasLeadingTrailingWhitespace(item));
            }
        }

        [TestMethod]
        public void GetDecimal_returns_correctly_converted_value_if_not_null()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            var val = config.GetDecimal("testDecimal");

            Assert.IsInstanceOfType(val, typeof(decimal));
        }

        [TestMethod, ExpectedException(typeof(ArgumentException))]
        public void GetDecimal_throws_exception_if_value_cannot_be_converted()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            config.GetDecimal("testKey");     // this value is a string and can't be converted.
        }

        [TestMethod]
        public void GetDouble_returns_correctly_converted_value_if_not_null()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            var val = config.GetDouble("testDouble");

            Assert.IsInstanceOfType(val, typeof(double));
        }

        [TestMethod, ExpectedException(typeof(ArgumentException))]
        public void GetDouble_throws_exception_if_value_cannot_be_converted()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            config.GetDouble("testKey");     // this value is a string and can't be converted.
        }

        [TestMethod]
        public void GetInt_returns_correctly_converted_value_if_not_null()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            var val = config.GetInt("testInteger");

            Assert.IsInstanceOfType(val, typeof(int));
        }

        [TestMethod, ExpectedException(typeof(ArgumentException))]
        public void GetInt_throws_exception_if_value_cannot_be_converted()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            config.GetInt("testKey");     // this value is a string and can't be converted.
        }

        public bool HasLeadingTrailingWhitespace(string str)
        {
            string pattern = @"^[ \t]+|[ \t]+$";
            Regex reg = new Regex(pattern, RegexOptions.IgnoreCase);
            var result = reg.IsMatch(str);
            return result;
        }

        [TestMethod]
        public void Default_instantiation_returns_default_configuration()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            Assert.IsNotNull(config);
        }

        [TestMethod]
        public void Default_instantiation_returns_config_values()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            Assert.IsTrue((config.Count > 0));
        }

        [TestMethod]
        public void All_config_values_return_non_null_through_index_accessor()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration();
            var configDict = (Dictionary<string, string>)config;

            if (config.Count > 0)
            {
                foreach (var val in configDict.Select(item => config[item.Key]))
                {
                    Assert.IsNotNull(val);
                }
            }
        }

        [TestMethod]
        public void Specific_instantiation_returns_specific_configuration_instance()
        {
            ConfigurationService.Sources.Clear();
            var config = ConfigurationService.GetConfiguration<DatabaseConfigurationSource>();

            Assert.IsInstanceOfType(config, typeof(IConfiguration));
            Assert.IsInstanceOfType(config, typeof(SimpleConfiguration));
        }

        public void Specific_instantiation_returns_specific_configuration_instance_alternate()
        {
            ConfigurationService.Sources.Clear();
            ConfigurationService.Sources.Add(typeof(DatabaseConfigurationSource));
            var config = ConfigurationService.GetConfiguration();

            Assert.IsInstanceOfType(config, typeof(IConfiguration));
            Assert.IsInstanceOfType(config, typeof(SimpleConfiguration));
        }

        [TestMethod]
        public void Multiple_config_sources_return_one_config_object()
        {
            ConfigurationService.Sources.Clear();
            ConfigurationService.Sources.Add(typeof(DatabaseConfigurationSource));
            ConfigurationService.Sources.Add(typeof(IniConfigurationSource));
            var config = ConfigurationService.GetConfiguration();

            Assert.IsTrue(config.Count > 3);
        }

        [TestMethod]
        public void Multiple_config_source_duplicate_values_last_in_persists()
        {
            ConfigurationService.Sources.Clear();
            ConfigurationService.Sources.Add(typeof(DatabaseConfigurationSource));
            ConfigurationService.Sources.Add(typeof(IniConfigurationSource));
            var config = ConfigurationService.GetConfiguration();

            Assert.AreNotEqual("from:DatabaseConfigurationSource", config["dupvalue"]);
            Assert.AreEqual("from:IniConfigurationSource", config["dupvalue"]);
        }

        [TestMethod]
        public void Clearing_source_property_will_remove_default_configurationsource()
        {
            Assert.IsTrue(ConfigurationService.Sources.Count == 1);
            // first we test without clearing
            ConfigurationService.Sources.Add(typeof (DatabaseConfigurationSource));
            Assert.IsTrue(ConfigurationService.Sources.Count == 2);

            // now we clear and expect only one
            ConfigurationService.Sources.Clear();
            ConfigurationService.Sources.Add(typeof(DatabaseConfigurationSource));
            Assert.IsTrue(ConfigurationService.Sources.Count == 1);

        }
    }
}
